package com.cardone.generator.template;

import java.io.File;
import java.util.List;
import java.util.Map;

import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;

import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.ui.freemarker.FreeMarkerTemplateUtils;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;

import com.cardone.common.template.util.TemplateUtils;
import com.cardone.generator.mapper.BusinessMapper;
import com.cardone.generator.mapper.EntityMapper;
import com.cardone.generator.mapper.ModuleMapper;
import com.cardone.generator.mapper.PoMapper;
import com.cardone.generator.mapper.ProjectMapper;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;

import freemarker.ext.beans.BeansWrapper;
import freemarker.template.Configuration;

/**
 * spring + spring mvc + spring jdbc
 *
 * @author yaohaitao
 */
@Getter
@Setter
@Accessors(chain = true)
@Slf4j
public class RunSSSTemplate implements RunTemplate {
	private Configuration configuration;

	private Map<String, String> contextMap = Maps.newHashMap();

	private String extendName = "Oracle";

	private Map<String, Object> model = Maps.newHashMap();

	private String outputDir;

	private boolean overrideFile = true;

	private ProjectMapper projectMapper;

	private List<String> serialVersionUIDList = Lists.newArrayList();

	@Override
	public List<PoMapper> findListPoMapper() {
		final List<PoMapper> poMapperList = Lists.newArrayList();

		if (CollectionUtils.isEmpty(this.projectMapper.getModuleMapperList())) {
			return poMapperList;
		}

		for (final ModuleMapper moduleMapper : this.projectMapper.getModuleMapperList()) {
			if (CollectionUtils.isEmpty(moduleMapper.getBusinessMapperList())) {
				continue;
			}

			for (final BusinessMapper businessMapper : moduleMapper.getBusinessMapperList()) {
				if (StringUtils.isEmpty(businessMapper.getTableName())) {
					continue;
				}

				final EntityMapper entityMapper = new EntityMapper();

				entityMapper.setCode(businessMapper.getCode());
				entityMapper.setTableName(businessMapper.getTableName());

				businessMapper.setEntityMapper(entityMapper);

				poMapperList.add(entityMapper);
			}
		}

		return poMapperList;
	}

	/**
	 * 初始化
	 */
	public void init() {
		Assert.notNull(this.projectMapper);

		Assert.notNull(this.outputDir);
	}

	private void initContext() {
		final String packageDir = StringUtils.replace(this.projectMapper.getPackageCode(), ".", File.separator);

		this.contextMap.put("outputDir", this.outputDir);

		this.contextMap.put("packageDir", packageDir);

		this.model.put("statics", BeansWrapper.getDefaultInstance().getStaticModels());

		this.model.put("projectMapper", this.projectMapper);

		this.model.put("packageCode", this.projectMapper.getPackageCode());

		this.model.put("extendName", this.extendName);

		this.model.put("projectCode", this.projectMapper.getCode());

		this.contextMap.put("extendName", this.extendName);

		this.contextMap.put("projectCode", this.projectMapper.getCode());
	}

	private void makeFile(final String templateString, final String templateName, final Boolean overrideFile) throws Exception {
		RunSSSTemplate.log.info(templateString);

		RunSSSTemplate.log.info(templateName);

		final String filePathName = TemplateUtils.processString(templateString, this.contextMap);

		RunSSSTemplate.log.info(filePathName);

		final File file = new File(filePathName);

		if (file.exists()) {
			RunSSSTemplate.log.info("file.exists():true");

			if (this.overrideFile && overrideFile) {
				FileUtils.deleteQuietly(file);
			} else {
				return;
			}
		}

		final freemarker.template.Template template = this.configuration.getTemplate(templateName);

		this.modelPutSerialVersionUID();

		final String data = FreeMarkerTemplateUtils.processTemplateIntoString(template, this.model);

		FileUtils.writeStringToFile(file, data);
	}

	private void markController() throws Exception {
		final String javaTemplateString = "${outputDir!}/src/main/java/${packageDir!}/${moduleMapperCode!}/controller/${filename!}Controller.java";

		this.makeFile(javaTemplateString, "Controller.ftl", true);
	}

	private void markDao() throws Exception {
		String javaTemplateString = "${outputDir!}/src/main/java/${packageDir!}/${moduleMapperCode!}/dao/${filename!}Dao.java";

		this.makeFile(javaTemplateString, "Dao.java.ftl", true);

		javaTemplateString = "${outputDir!}/src/main/java/${packageDir!}/${moduleMapperCode!}/dao/${filename!}JdbcDao.java";

		this.makeFile(javaTemplateString, "JdbcDao.java.ftl", true);
	}

	private void markDto() throws Exception {
		final String javaTemplateString = "${outputDir!}/src/main/java/${packageDir!}/${moduleMapperCode!}/dto/${filename!}Dto.java";

		this.makeFile(javaTemplateString, "Dto.java.ftl", true);
	}

	private void markPo() throws Exception {
		final String javaTemplateString = "${outputDir!}/src/main/java/${packageDir!}/${moduleMapperCode!}/po/${filename!}.java";

		this.makeFile(javaTemplateString, "Po.java.ftl", true);
	}

	private void markService() throws Exception {
		String javaTemplateString = "${outputDir!}/src/main/java/${packageDir!}/${moduleMapperCode!}/service/${filename!}Service.java";

		this.makeFile(javaTemplateString, "Service.java.ftl", true);

		javaTemplateString = "${outputDir!}/src/main/java/${packageDir!}/${moduleMapperCode!}/service/${filename!}DefaultService.java";

		this.makeFile(javaTemplateString, "DefaultService.java.ftl", true);
	}

	private void markSpringConfig() throws Exception {
		final String javaTemplateString = "${outputDir!}/src/main/resources/${projectCode!}/${moduleMapperCode!}/applicationContext-${filename!}.xml";

		this.makeFile(javaTemplateString, "applicationContext-service.ftl", true);
	}

	private void markSQL() throws Exception {
		final String root = "${outputDir!}/src/main/resources/${projectCode!}/sql/generator/${extendName?uncap_first}/${projectCode!}/${moduleMapperCode!}/${businessCode?uncap_first}/";

		String javaTemplateString = root + "updateByCode.ftl";

		this.makeFile(javaTemplateString, "updateByCode.ftl", true);

		javaTemplateString = root + "updateByIds.ftl";

		this.makeFile(javaTemplateString, "updateByIds.ftl", true);

		javaTemplateString = root + "findByCode.ftl";

		this.makeFile(javaTemplateString, "findByCode.ftl", true);

		javaTemplateString = root + "readByCode.ftl";

		this.makeFile(javaTemplateString, "readByCode.ftl", true);

		javaTemplateString = root + "insertByCode.ftl";

		this.makeFile(javaTemplateString, "insertByCode.ftl", true);

		javaTemplateString = root + "insertByNotExistsCode.ftl";

		this.makeFile(javaTemplateString, "insertByNotExistsCode.ftl", true);

		javaTemplateString = root + "deleteByCode.ftl";

		this.makeFile(javaTemplateString, "deleteByCode.ftl", true);

		javaTemplateString = root + "deleteByIds.ftl";

		this.makeFile(javaTemplateString, "deleteByIds.ftl", true);

		javaTemplateString = root + "whereByCode.ftl";

		this.makeFile(javaTemplateString, "whereByCode.ftl", true);
	}

	private void modelPutSerialVersionUID() {
		String serialVersionUID = RandomStringUtils.randomNumeric(18);

		while (StringUtils.startsWithAny(serialVersionUID, new String[] { "0", "9" }) || this.serialVersionUIDList.contains(serialVersionUID)) {
			serialVersionUID = RandomStringUtils.randomNumeric(18);
		}

		this.serialVersionUIDList.add(serialVersionUID);

		this.model.put("serialVersionUID", serialVersionUID);
	}

	@Override
	public void run() throws Exception {
		if (CollectionUtils.isEmpty(this.projectMapper.getModuleMapperList())) {
			return;
		}

		this.initContext();

		for (final ModuleMapper moduleMapper : this.projectMapper.getModuleMapperList()) {
			this.contextMap.put("moduleMapperCode", moduleMapper.getCode());

			this.model.put("moduleMapperCode", moduleMapper.getCode());

			this.model.put("moduleMapper", moduleMapper);

			if (CollectionUtils.isEmpty(moduleMapper.getBusinessMapperList())) {
				continue;
			}

			for (final BusinessMapper businessMapper : moduleMapper.getBusinessMapperList()) {
				businessMapper.setName(StringUtils.defaultString(businessMapper.getName(), moduleMapper.getName()));

				this.setBusiness(businessMapper);

				if (businessMapper.getEntityMapper() != null) {
					this.markPo();

					this.markDto();

					this.markSQL();
				}

				this.markController();

				this.markDao();

				this.markService();

				this.markSpringConfig();
			}

			this.model.put("projectMapper", this.projectMapper);
		}
	}

	private void setBusiness(final BusinessMapper businessMapper) {
		this.contextMap.put("filename", businessMapper.getCode());

		this.contextMap.put("businessCode", businessMapper.getCode());

		this.model.put("businessCode", businessMapper.getCode());

		this.model.put("entityMapper", businessMapper.getEntityMapper());

		if (businessMapper.getEntityMapper() == null) {
			this.model.put("businessName", StringUtils.defaultIfBlank(businessMapper.getName(), businessMapper.getCode()));
		} else {
			this.model.put("businessName", StringUtils.defaultIfBlank(businessMapper.getEntityMapper().getRemarks(), businessMapper.getEntityMapper().getCode()));
		}

		this.model.put("businessMapper", businessMapper);
	}
}
